#include "ComPort.h"
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <string.h>

//#define COM_PORT "/dev/cuaU0" // BSD style
//#define COM_PORT "/dev/ttyUSB0" // Linux style
//#define BAUD_RATE B38400  // faire off/on de la carte si changement de vitesse

/*
USB-I2C Module
Command  	Value  	Description  	Available in I2C-USB Version
I2C_SGL 	0x53 	Read/Write single byte for non-registered devices, such as the Philips PCF8574 I/O chip. 	All
I2C_MUL 	0x54 	Read multiple bytes without setting new address (eeprom's, Honeywell pressure sensors, etc). 	V5 and higher
I2C_AD1 	0x55 	Read/Write single or multiple bytes for 1 byte addressed devices (the majority of devices will use this one) 	All
I2C_AD2 	0x56 	Read/Write single or multiple bytes for 2 byte addressed devices, eeproms from 32kbit (4kx8) and up.  	V6 and higher
I2C_USB 	0x5A 	A range of commands to the USB-I2C module, generally to improve selected communications or provide analogue/digital I/O All
*/

// Si4735 adresse = 34
// Avant toute communication avec le Si4735, il faut tirer au niveau bas sa broche RST (pin 5)


int ComPort::getUSBI2CFirmwareRevision()
{
	  int res;
		char cmd[4];
		    //printf("sending 4 data bytes to get Revision Number...\n");
		    cmd[0] = 0x5A;  // USB-I2C module Read/Write command
		    cmd[1] = 0x01;  // Get Firmware Revison
		    cmd[2] = 0x00;  // not used
		    cmd[3] = 0x00;  // not used
		    res = write(com_port_fd,cmd,4);
		     tcdrain(com_port_fd);
		    //printf("data sent.\n");
		    if ( res < 0 )
		     perror("ERROR - ComPort - getRevison()");
		return res;
}

int ComPort::readBytes(char *data, int numberOfBytes)
{
  int res;
    res = read(com_port_fd, data, numberOfBytes);
  return res;
}

int ComPort::readBytesFromOneByteAddressedI2CDevice(char address, char command, int numberOfBytesToRead) //char address, char command, char *data, int datalenght)
{
	int res;
	 char cmd[4];
//	    printf("reading %d data bytes...\n", numberOfBytesToRead);
	     cmd[0] = 0x55;  				 // USB-I2C module Read/Write command
	     cmd[1] = address | 0b00000001;   // device address bit0 = 0 for writing, bit0 = 1 for reading
	     cmd[2] = command;
	     cmd[3] = numberOfBytesToRead;  	// number of bytes to read
	     res = write(com_port_fd,cmd,4);
	      //tcdrain(com_port_fd);
//		   printf("Read command sent.\n");
		  if ( res < 0 )
		   perror("ERROR - ComPort - readBytesFromOneByteAddressedI2CDevice()");
	return res;
}

int ComPort::writeBytesToOneByteAddressedI2CDevice(char address, char command, char *data, int datalenght)
{
   int res;
	char cmd[64];
	    //printf("sending %d data bytes...\n", datalenght);
	    cmd[0] = 0x55;  				 // USB-I2C module Read/Write command
	    cmd[1] = address & 0b11111110;   // device address bit0 = 0 for writing, bit0 = 1 for reading
	    cmd[2] = command;
	    cmd[3] = datalenght;  	// number of bytes to write
	    for(int i=0; i < datalenght; i++)
	     cmd[4+i] = data[i];  	// data
	    res = write(com_port_fd,cmd,4 + datalenght);
	    tcdrain(com_port_fd);
	    //printf("data sent.\n");
	    if ( res < 0 )
	     perror("ERROR - ComPort - writeBytesToOneByteAddressedI2CDevice()");
	return res;
}

// TO FIX - TO TEST
int ComPort::writeBytesToTwoByteAddressedI2CDevice(char address, char command1, char command2, char *data, int datalenght)
{
   int res;
	char cmd[64];
	    printf("sending %d data bytes...\n", datalenght);
	    cmd[0] = 0x56;  				 // USB-I2C module Read/Write command
	    cmd[1] = address & 0b11111110;   // device address bit0 = 0 for writing, bit0 = 1 for reading
	    cmd[2] = command1;
	    cmd[3] = command2;
	    cmd[4] = datalenght;  	// number of bytes to write
	    for(int i=0; i < datalenght; i++)
	     cmd[5+i] = data[i];  	// data
	    res = write(com_port_fd,cmd,5 + datalenght);

	    printf("data sent.\n");
	    if ( res < 0 )
	     perror("ERROR - ComPort - writeBytesToTwoByteAddressedI2CDevice()");
	return res;
}

// TO FIX - TO TEST
int ComPort::readBytesFromTwoByteAddressedI2CDevice(char address, char command1, char command2,  char *data, int bytesToRead) //char address, char command, char *data, int datalenght)
{
	   int res;
	  	char cmd[64];
		    printf("reading %d data bytes...\n", bytesToRead);
			    cmd[0] = 0x56;  				 // USB-I2C module Read/Write command
			    cmd[1] = address | 0b00000001;   // device address bit0 = 0 for writing, bit0 = 1 for reading
			    cmd[2] = command1;
			    cmd[3] = command2;
			    cmd[4] = bytesToRead;  	// number of bytes to read
			    //for(int i=0; i < datalenght; i++)
			    // cmd[4+i] = data[i];  	// data
			    res = write(com_port_fd,cmd,5);

			    for(long d=0; d < 100000000; d++)
			    	;

	     	    res = read(com_port_fd, data, bytesToRead);

			    printf("data read.\n");
			    if ( res < 0 )
			     perror("ERROR - ComPort - readBytesFromOneByteAddressedI2CDevice()");

			return res;

}



ComPort::ComPort(int device)
{
  ComPort::dev = new char[MAX_DEV_SIZE];
  switch( device )
  {
	case ComPort::LCD_LK :
							const char *dev;
							dev = findLCDLKtty();
							strncpy(ComPort::dev, dev, strlen(dev)+1);
							ComPort::baudrate= B19200;
							ComPort::stopbit = 0;
							break;
	default :
							strncpy(ComPort::dev, "/dev/null", strlen("/dev/null")+1); ;
							std::cout << "Device inconnu !\n";
  }
}

ComPort::ComPort(const char *dev, int baudrate, int stopbit)
{
   ComPort::dev = new char[256];
   strncpy(ComPort::dev, dev, strlen(dev)+1);
   switch ( baudrate )
   {
	 case 110    : ComPort::baudrate = B110; break;
	 case 300    : ComPort::baudrate = B300; break;
	 case 1200   : ComPort::baudrate = B1200; break;
	 case 2400   : ComPort::baudrate = B2400; break;
	 case 4800   : ComPort::baudrate = B4800; break;
	 case 9600   : ComPort::baudrate = B9600; break;
	 case 19200  : ComPort::baudrate = B19200; break;
	 case 38400  : ComPort::baudrate = B38400; break;
	 case 57600  : ComPort::baudrate = B57600; break;
	 case 115200 : ComPort::baudrate = B115200; break;
	 case 230400 : ComPort::baudrate = B230400; break;
	 default	 : ComPort::baudrate = B9600;
	               printf("Warning - unknown baudrate value,(%d ?) using default: 9600 baud\n",baudrate);
   }
   switch ( stopbit )
   {
     case 1  	 : ComPort::stopbit = 0; break;
     case 2 	 : ComPort::stopbit = CSTOPB; break;
     default	 : ComPort::stopbit = 0;
				   printf("Warning - unknown stopbit value (%d ?) , using default: 1 stop bit\n",stopbit);
   }
}

ComPort::~ComPort()
{
	close(com_port_fd);
}

void signal_handler_IO(int status)
{
  status = status+1-1;
// printf("received SIGIO signal !\n");
 /*
 res = read(status, &key, sizeof(key));
 if ( res < 0 )
	perror("ERROR - ComPort - :");

 //ComPort::getPort();

 printf("Key=%d",key);
*/
 //wait_flag=FALSE;
}

// Retourne le descripteur du Port
int ComPort::getPort()
{
	return com_port_fd;
}

void ComPort::closePort()
{
	close(com_port_fd);
}

void ComPort::openPort(void)
{
		  int e;
		  struct termios oldtio,newtio;
		  struct sigaction saio;

		  std::cout << "Opening port [" <<  ComPort::dev  << "]..." << std::endl;

		  if ( strcmp("/dev/null", ComPort::dev) == 0 )
		  {
			  std::cout << "Nothing to do with [" <<  ComPort::dev  << "]" << std::endl;
			  return;
		  }
		  // !!! il y a un bug sur open avec Ubuntu 10.10
		  // found as an odd workaround though that if I run "screen /dev/ttyUSB0" and then kill screen (this only appears to work if I kill it, not if I close it nicely) I can then use /dev/ttyUSB0 properly.
		  // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/661321
		  //printf("Before Open...\n");
		  // NDELAY pour les version Ubuntu > 9.x
		  com_port_fd = open(dev,O_RDWR|O_NOCTTY|O_NDELAY);
		  //printf("...After Open\n");
		  if( com_port_fd < 0 )
		  {
			 perror("ERROR - ComPort - openPort():");
		    // exit(-1);
		  }

		  // install signal handler before making the device asynchronous
		  saio.sa_handler = signal_handler_IO;
		  //printf("handler\n");
		  //saio.sa_mask=0;
		  saio.sa_flags=0;
		  sigaction(SIGIO,&saio,NULL);
		  //printf("sigaction\n");
		  // register signal
		  signal(SIGIO,signal_handler_IO);
		  signal(SIGTERM,signal_handler_IO);
		  signal(SIGHUP,signal_handler_IO);
		  //printf("register\n");
		  // make the file descriptor asynchronous
		  if ( fcntl(com_port_fd, F_SETFL,O_ASYNC) < 0 ) perror("F_SETFL");

		  // allow the process to receive SIGIO
		  // if ( ioctl(fd,F_IOSETOWN) < 0 ) perror("ioctl ");
		  // OpenBSD is blocking /disabling ?? this for security reasons -> must prove by PID ?
		  // http://insecure.org/sploits/io.asynch.signlal.handling.html
		  if ( fcntl(com_port_fd, F_SETOWN,getpid()) < 0 ) perror("F_SETOWN");

		  // save current settings
		  e=tcgetattr(com_port_fd, &oldtio);
		  if( e<0 )
		  {
			perror("ERROR - ComPort - when saving settings openPort()");
			//exit(-1);
		  }
		  //bzero(&newtio,sizeof(newtio));  // met a zero les bytes Obsolete
		  memset(&newtio, '\0', sizeof(newtio));

		  std::cout << "Setting speed & co" << std::endl;
		  // c_cflag =  Control options
          /// ! USB - I2c : 8 bit - no parity - 2 STOP BIT !!
		  //newtio.c_cflag = baudrate | CS8 | CSTOPB | CREAD | CLOCAL;  // CS8=8bit,no parity,2 STOP BIT, CLOCAL=ignore modem status lines
		  //newtio.c_cflag =  baudrate | CS8 | CREAD; // ! baudrate = Bxxxx, pas 9600 par exemple.
		  newtio.c_cflag = baudrate | stopbit | CS8 | CREAD | CLOCAL;
		  // Input Options
		  newtio.c_iflag = 0; // raw input
		  // Output Options
		  newtio.c_oflag = 0; // raw output
		  // Line Options
		  newtio.c_lflag = 0; // set input mode: canonical/non canonical, no echo, etc.

		  // if non-canonical
		  newtio.c_cc[VTIME] = 0; // inter-character timer unused
		  // !!!!!! VMIN = 1 pour le module DLP
		  // !!!!!! VMIN = 0 pour les autres jusqu'a present !!!!

		  //Ubuntu 10.10 ? newtio.c_cc[VMIN] = 1;  // blocking read until x chars read
		  newtio.c_cc[VMIN] = 0;  // blocking read until x chars read

		  /*
		  // for OpenBSD, the speed must be set by cfsetspeed
		  e=cfsetspeed(&newtio,BAUD_RATE);
		  if(e<0){perror("ERROR - ComPort - SetSpeed ");exit(-1);}
	    */

		  e = tcflush(com_port_fd, TCIFLUSH);
		  if( e < 0 )
		  { perror("ERROR - ComPort - Flush openPort()");
		   //  exit(-1);
		  }

		  e = tcsetattr(com_port_fd,TCSANOW,&newtio);
		  if( e < 0 )
		  { perror("ERROR - ComPort - in tcsetattr openPort()");
		   // exit(-1);
		  }
   std::cout << "Port opened." << std::endl;
}

// LKxxx series USB Matrix Orbital Displays
// http://www.matrixorbital.com/Intelligent-Display-Character-LCDs/c39_3/index.html

// Recherche sur quel ttyUSBx l'afficheur Matrix Orbital type LK est connecte
const char* ComPort::findLCDLKtty()
{
	/* PLUTOT UTILISER DU C++ PUR ???
	// ! Si Linux udev
    QDir dir("/dev/serial/by-id");
    QFileInfoList fileInfoList = dir.entryInfoList();
    QListIterator<QFileInfo>  it(fileInfoList);;
    QFileInfo fileInfo;
    while ( it.hasNext() )
    {
    	fileInfo = it.next();
    	const QString name = fileInfo.fileName();
    	//if ( name.contains("MO_LK") )
    	if ( name.contains("MO_LCDTracto") )
    	{
    	  //std::cout << "Lien sur afficheur Matrix Ortibal =" << name.toStdString() << "\n";
    	  const QString link = fileInfo.readLink();
    	  std::cout << "LCD Matrix Orbital LK sur = " << link.toStdString() << "\n";
    	  return link.toStdString().c_str();
    	}
    }
    */
   std::cout << "NO LCD found !" << "\n";
   return "/dev/null";
}
char ComPort::readKeyLK()
{
	 res = read(com_port_fd, &key, sizeof(key));
	 if ( res < 0 )
		perror("ERROR - ComPort - readKeyLK()");
	 return key;
}
void ComPort::setTextLK(const char *txt)
{
	 res = write(com_port_fd, txt, strlen(txt));
	 if ( res < 0 )
		perror("ERROR - ComPort - setTextLK()");
}
void ComPort::setCharacterLK(char car)
{
	 res = write(com_port_fd, &car, 1);
	 if ( res < 0 )
		perror("ERROR - ComPort - setCharacterLK()");
}
void ComPort::clearScreenLK()
{
	cmd[0] = 254;
	cmd[1] = 88;
	 res = write(com_port_fd,cmd, 2);
	 if ( res < 0 )
		perror("ERROR - ComPort - clearScreenLK()");
}
// L'origine du Display se trouve a (1,1)
void ComPort::setCursorPositionLK(char row, char col)
{
	cmd[0] = 254;
	cmd[1] = 71;
	cmd[2] = col;
	cmd[3] = row;
	 res = write(com_port_fd,cmd,4);
	 if ( res < 0 )
		perror("ERROR - ComPort - setCursorPositionLK()");
}
void ComPort::blinkingBlockCursorOnLK()
{
	cmd[0] = 254;
	cmd[1] = 83;
	 res = write(com_port_fd,cmd,2);
	 if ( res < 0 )
		perror("ERROR - ComPort - blinkingBlockCursorOnLK()");
}
int ComPort::readVersionNumberLK()
{
    char versionNumber;
	cmd[0] = 254;
	cmd[1] = 54;
	 res = write(com_port_fd,cmd,2);
	 if ( res < 0 )
		perror("ERROR - ComPort - readVersionNumberLK()");
	 res = read(com_port_fd, &versionNumber, sizeof(versionNumber));
	 if ( res < 0 )
		perror("ERROR - ComPort - readVersionNumberLK()");
	 return versionNumber;
}
int ComPort::readModuleTypeLK()
{
    char moduleType;
	cmd[0] = 254;
	cmd[1] = 55;
	 res = write(com_port_fd,cmd,2);
	 if ( res < 0 )
		perror("ERROR - ComPort - readModuleTypeLK()");
	 res = read(com_port_fd, &moduleType, sizeof(moduleType));
	 if ( res < 0 )
		perror("ERROR - ComPort - readModuleTypeLK()");
	 return moduleType;
}

// DLP Module
char ComPort::DLP_Ping()
{
    char pingResult[16];
	cmd[0] = 0x27; // 0x27
	 res = write(com_port_fd,cmd,1);
	 if ( res < 0 )
		perror("ERROR - ComPort - DLP_Ping()");
	 res = read(com_port_fd, pingResult, 16);
	 if ( res < 0 )
		perror("ERROR - ComPort - DLP_Ping()");
	 return pingResult[0]; // retourne 'Q' (0x51) si le module DLP est trouve sur ce port
}

void ComPort::DLP_ChannelHigh(int channel)
{
	switch( channel )
	{
		case 1 : cmd[0] = 0x31; break;
		case 2 : cmd[0] = 0x32; break;
		case 3 : cmd[0] = 0x33; break;
		case 4 : cmd[0] = 0x34; break;
		case 5 : cmd[0] = 0x35; break;
		case 6 : cmd[0] = 0x36; break;
		case 7 : cmd[0] = 0x37; break;
		case 8 : cmd[0] = 0x38; break;
		default : return;
	}
	res = write(com_port_fd,cmd,1);
	 if ( res < 0 )
		perror("ERROR - ComPort - DLP_ChannelHigh()");
}
void ComPort::DLP_ChannelLow(int channel)
{
	switch( channel )
	{
		case 1 : cmd[0] = 0x51; break;
		case 2 : cmd[0] = 0x57; break;
		case 3 : cmd[0] = 0x45; break;
		case 4 : cmd[0] = 0x52; break;
		case 5 : cmd[0] = 0x54; break;
		case 6 : cmd[0] = 0x59; break;
		case 7 : cmd[0] = 0x55; break;
		case 8 : cmd[0] = 0x49; break;
		default : return;
	}
	res = write(com_port_fd,cmd,1);
	 if ( res < 0 )
		perror("ERROR - ComPort - DLP_ChannelLow()");
}
void ComPort::DLP_Channel8High()
{
	cmd[0] = 0x38;
	res = write(com_port_fd,cmd,1);
	 if ( res < 0 )
		perror("ERROR - ComPort - DLP_Channel8High()");
}
void ComPort::DLP_Channel8Low()
{
	cmd[0] = 0x49;
	 res = write(com_port_fd,cmd,1);
	 if ( res < 0 )
		perror("ERROR - ComPort - DLP_Channel8Low()");
}
